#include "string.h"
#include "st7789.h"

#define TAG "ST7789"

static int st7789_set_brightness(struct device* dev, int brightness)
{
    const struct st7789_config *cfg = dev->config->config_info;
    ledc_channel_t channel = cfg->backlight_channel.channel;

    if (brightness > 100) {
        brightness = 100;
    } else if (brightness < 0) {
        brightness = 0;
    }

    ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness);
    // LEDC resolution set to 10bits, thus: 100% = 1023
    uint32_t duty_cycle = (1023 * brightness) / 100;

    ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, channel, duty_cycle));
    ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, channel));

    return ESP_OK;
}

static void st7789_set_color(struct device* dev, uint16_t color)
{
    const struct st7789_config *cfg = dev->config->config_info;
    struct st7789_data *data = dev->driver_data;
    // 分配内存 这里分配了液晶屏一行数据需要的大小
    uint16_t *buffer = (uint16_t *)heap_caps_malloc(cfg->width * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
    
    if (NULL == buffer)
    {
        ESP_LOGE(TAG, "Memory for bitmap is not enough");
    }
    else
    {
        for (size_t i = 0; i < cfg->width; i++) // 给缓存中放入颜色数据
        {
            buffer[i] = color;
        }
        for (int y = 0; y < cfg->height; y++) // 显示整屏颜色
        {
            esp_lcd_panel_draw_bitmap(data->panel_handle, 0, y, cfg->width, y+1, buffer);
        }
        free(buffer);
    }
}

static void st7789_draw_pictrue(struct device* dev, int x_start, int y_start, int x_end, int y_end, const unsigned char *gImage)
{
    // 分配内存 分配了需要的字节大小 且指定在外部SPIRAM中分配
    struct st7789_data *data = dev->driver_data;

    size_t pixels_byte_size = (x_end - x_start)*(y_end - y_start) * 2;
    uint16_t *pixels = (uint16_t *)heap_caps_malloc(pixels_byte_size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
    if (NULL == pixels) {
        ESP_LOGE(TAG, "Memory for bitmap is not enough");
        return;
    }

    memcpy(pixels, gImage, pixels_byte_size);  // 把图片数据拷贝到内存
    esp_lcd_panel_draw_bitmap(data->panel_handle, x_start, y_start, x_end, y_end, (uint16_t *)pixels); // 显示整张图片数据
    heap_caps_free(pixels);  // 释放内存
}

static int st7789_brightness_init(struct device* dev)
{
    const struct st7789_config *cfg = dev->config->config_info;

    ledc_timer_config_t LCD_backlight_timer = cfg->backlight_timer;
    ledc_channel_config_t LCD_backlight_channel = cfg->backlight_channel;

    ESP_ERROR_CHECK(ledc_timer_config(&LCD_backlight_timer));
    ESP_ERROR_CHECK(ledc_channel_config(&LCD_backlight_channel));

    return ESP_OK;
}

static int st7789_display_init(struct device* dev)
{
    const struct st7789_config *cfg = dev->config->config_info;
    struct st7789_data *data = dev->driver_data;
    esp_err_t ret = ESP_OK;

    // 背光初始化
    ESP_RETURN_ON_ERROR(
        st7789_brightness_init(dev), 
        TAG, "Brightness init failed");
    // spi初始化
    ESP_RETURN_ON_ERROR(
        spi_bus_initialize(cfg->host, &cfg->buscfg, SPI_DMA_CH_AUTO), 
        TAG, "SPI init failed");
    // 液晶屏控制IO初始化
    ESP_LOGD(TAG, "Install panel IO");
    ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)cfg->host, &cfg->io_config, &data->io_handle), 
        err, TAG, "New panel IO failed");
    // 初始化液晶屏驱动芯片ST7789
    ESP_LOGD(TAG, "Install LCD driver");
    ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(data->io_handle, &cfg->panel_config, &data->panel_handle), 
        err, TAG, "New panel failed");
    
    esp_lcd_panel_reset(data->panel_handle);  // 液晶屏复位

    sys_set_lcd_cs_level(0);  // 拉低CS引脚

    esp_lcd_panel_init(data->panel_handle);  // 初始化配置寄存器
    esp_lcd_panel_invert_color(data->panel_handle, true); // 颜色反转
    esp_lcd_panel_swap_xy(data->panel_handle, true);  // 显示翻转
    esp_lcd_panel_mirror(data->panel_handle, true, false); // 镜像

    st7789_set_color(dev, 0x0000);

    esp_lcd_panel_disp_on_off(data->panel_handle, true);// 打开液晶屏显示

    return ret;

err:
    if (data->panel_handle) {
        esp_lcd_panel_del(data->panel_handle);
    }
    if (data->io_handle) {
        esp_lcd_panel_io_del(data->io_handle);
    }
    spi_bus_free(cfg->host);

    return ret;
}

static int st7789_lcd_init(struct device *dev)
{
    esp_err_t ret = ESP_OK;

    ret = st7789_display_init(dev); // 液晶屏驱动初始化
    ret = st7789_set_brightness(dev, 100); // 打开背光显示

    return  ret;
}

const static struct st7789_config st7789_default_config = {
    .width = BSP_LCD_W_RES,
    .height = BSP_LCD_H_RES,
    .host = BSP_LCD_SPI_NUM,
    .buscfg = {
        .sclk_io_num = BSP_LCD_SPI_CLK,
        .mosi_io_num = BSP_LCD_SPI_MOSI,
        .miso_io_num = GPIO_NUM_NC,
        .quadwp_io_num = GPIO_NUM_NC,
        .quadhd_io_num = GPIO_NUM_NC,
        .max_transfer_sz = BSP_LCD_W_RES * BSP_LCD_H_RES * sizeof(uint16_t),
    },
    .io_config = {
        .dc_gpio_num = BSP_LCD_DC,
        .cs_gpio_num = BSP_LCD_SPI_CS,
        .pclk_hz = BSP_LCD_PIXEL_CLOCK_HZ,
        .lcd_cmd_bits = LCD_CMD_BITS,
        .lcd_param_bits = LCD_PARAM_BITS,
        .spi_mode = 2,
        .trans_queue_depth = 10,
    },
    .panel_config = {
        .reset_gpio_num = BSP_LCD_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = BSP_LCD_BITS_PER_PIXEL,
    },
    .backlight_channel = {
        .gpio_num = BSP_LCD_BACKLIGHT,
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .channel = LCD_LEDC_CH,
        .intr_type = LEDC_INTR_DISABLE,
        .timer_sel = 0,
        .duty = 0,
        .hpoint = 0,
        .flags.output_invert = true
    },
    .backlight_timer = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .duty_resolution = LEDC_TIMER_10_BIT,
        .timer_num = 0,
        .freq_hz = 5000,
        .clk_cfg = LEDC_AUTO_CLK
    },
};

static struct st7789_data st7789_handle;

const struct lcd_device_api st7789_driver_api = {
   .set_brightness = st7789_set_brightness,
   .set_color = st7789_set_color,
   .draw_pictrue = st7789_draw_pictrue,
};

DEVICE_DEFINE(st7789, "st7789", st7789_lcd_init, &st7789_default_config, &st7789_driver_api, &st7789_handle, 
            PRE_KERNEL_2, 40);
